<!--编排执行和查看页面-->
<template>
  <div class="workflow-execution">
    <Card :bordered="false" dis-hover :padding="0">
      <div class="workflow-execution-header">
        <div class="back-header">
          <Icon size="22" type="md-arrow-back" class="icon" @click="handleBack" />
        </div>
        <div class="form">
          <Form label-position="left">
            <FormItem v-if="isEnqueryPage" :label-width="100" :label="$t('orchs')">
              <div style="display: flex">
                <CustomFlowSelect
                  v-model="selectedFlowInstance"
                  style="width: 60%"
                  :options="allFlowInstances"
                  @search="getProcessInstances"
                  @clear="clearHistoryOrch"
                  @change="queryHandler"
                  :disabled="['main', 'sub'].includes(from)"
                  :loading="allFlowInstancesLoading"
                  from="detail"
                />
                <!--暂停执行-->
                <Button
                  type="warning"
                  @click="flowControlHandler('stop')"
                  style="background-color: #826bea; border-color: #826bea; margin-left: 10px"
                  v-if="currentInstanceStatusForNodeOperation === 'InProgress'"
                  icon="md-pause"
                >{{ $t('be_pause') }}</Button>
                <!--继续执行-->
                <Button
                  type="success"
                  @click="flowControlHandler('recover')"
                  style="margin-left: 10px"
                  v-if="currentInstanceStatusForNodeOperation === 'Stop'"
                  icon="md-play"
                >{{ $t('be_continue') }}</Button>
                <!--终止执行-->
                <Button
                  v-if="
                    ['InProgress', 'Stop'].includes(currentInstanceStatusForNodeOperation) && !subProcBindParentFlag
                  "
                  type="warning"
                  @click="stopHandler"
                  style="margin-left: 10px"
                  icon="md-square"
                >{{ $t('stop_orch') }}</Button>
                <!--编排关联的ITSM工单-->
                <Poptip
                  v-if="Array.isArray(flowData.request) && flowData.request.length > 0"
                  placement="bottom"
                  trigger="hover"
                  width="500"
                  style="margin-left: 10px"
                >
                  <Button icon="md-person">ITSM工单</Button>
                  <div slot="content" style="padding: 3px 0px">
                    <div v-for="i in flowData.request" :key="i.id" style="padding: 3px 0">
                      <Icon type="md-person"></Icon>
                      <span style="color: #5384ff; cursor: pointer" @click="handleLinkItsmDetail(i)">{{ i.name }}</span>
                    </div>
                  </div>
                </Poptip>
              </div>
            </FormItem>
            <Col v-if="!isEnqueryPage" span="7">
              <FormItem :label-width="100" :label="$t('select_orch')">
                <Select
                  label
                  v-model="selectedFlow"
                  :disabled="Boolean(subProcId)"
                  @on-change="orchestrationSelectHandler"
                  @on-open-change="getAllFlow"
                  filterable
                  clearable
                  @on-clear="clearFlow"
                >
                  <Option v-for="item in allFlows" :value="item.procDefId" :key="item.procDefId">{{ item.procDefName }} [{{ item.procDefVersion }}] {{ item.createdTime }}</Option>
                </Select>
              </FormItem>
            </Col>
            <Col v-if="!isEnqueryPage" span="12" offset="0">
              <FormItem required :label-width="100" :label="$t('bc_execution_instance')">
                <Select
                  style="width: 80%"
                  label
                  v-model="selectedTarget"
                  :disabled="Boolean(subProcId)"
                  @on-change="onTargetSelectHandler"
                  @on-open-change="getTargetOptions"
                  filterable
                  clearable
                  @on-clear="clearTarget"
                >
                  <Option v-for="item in allTarget" :value="item.id" :key="item.id">{{ item.displayName }}</Option>
                </Select>
                <Button
                  :disabled="
                    isExecuteActive ||
                    !showExcution ||
                    !this.selectedTarget ||
                    !this.selectedFlow ||
                    !isShowExect ||
                    Boolean(subProcId)
                  "
                  :loading="btnLoading"
                  type="info"
                  @click="excutionFlow"
                  style="margin-left: 6px"
                >{{ $t('execute') }}</Button>
              </FormItem>
            </Col>
            <!--模板收藏功能-->
            <Col v-if="!isEnqueryPage && selectedFlow" span="5" style="text-align: right">
              <Tooltip :content="selectedFlowObj.collected ? $t('be_cancel_save') : $t('bc_save')" placement="left">
                <Icon
                  style="cursor: pointer; margin-right: 10px"
                  :size="28"
                  :color="selectedFlowObj.collected ? '#ebac42' : ''"
                  :type="selectedFlowObj.collected ? 'ios-star' : 'ios-star-outline'"
                  @click="handleStar(selectedFlowObj)"
                />
              </Tooltip>
            </Col>
          </Form>
        </div>
      </div>
      <Row id="graph-container">
        <Col span="7" style="border-right: 1px solid #d3cece; text-align: center; height: 100%; position: relative">
          <div class="graph-container" id="flow" style="height: 90%"></div>
          <!--重置-->
          <Button class="reset-button" size="small" @click="ResetFlow">ResetZoom</Button>
          <span class="set-data-title">{{ $t('fe_view_flow') }}</span>
          <!--预览数据-->
          <Button
            v-if="!isEnqueryPage && selectedFlow && selectedTarget"
            class="set-data-button"
            size="small"
            type="primary"
            @click="setFlowDataForAllNodes"
          >{{ $t('fe_node_datalist') }}</Button>
        </Col>
        <Col span="17" style="text-align: center; text-align: center; height: 100%; position: relative">
          <div class="graph-container" id="graph" style="height: 90%"></div>
          <!--重置-->
          <Button class="reset-button" size="small" @click="ResetModel">ResetZoom</Button>
          <span class="set-data-title">{{ $t('fe_preview_using_data') }}</span>
          <!--预览数据-->
          <Button
            v-if="selectedFlow && selectedTarget"
            class="set-data-button"
            size="small"
            type="primary"
            @click="showModelDataWithFlow"
          >{{ $t('fe_data_nodelist') }}</Button>
          <Spin size="large" fix v-if="isLoading || intervalLoading">
            <Icon type="ios-loading" size="44" class="spin-icon-load"></Icon>
            <div>{{ $t('loading') }}</div>
          </Spin>
        </Col>
      </Row>
      <Spin fix v-if="loading"></Spin>
    </Card>
    <!--左侧预览弹窗(新建)-->
    <BaseDrawer
      :title="$t('overview')"
      :visible.sync="flowNodesWithDataModalVisible"
      realWidth="70%"
      :scrollable="true"
      :maskClosable="false"
    >
      <template slot-scope="{maxHeight}" slot="content">
        <Table
          border
          :columns="flowNodesWithModelDataColums"
          :max-height="maxHeight"
          :data="allFlowNodesModelData"
          @on-selection-change="allFlowNodesSelectChange"
          :span-method="flowNodeDataHandleSpan"
        >
        </Table>
      </template>
      <template slot="footer">
        <Button type="default" @click="flowNodesWithDataModalVisible = false">{{ $t('bc_cancel') }}</Button>
        <Button type="primary" @click="flowNodesTargetModelConfirm" style="margin-left:8px;">{{ $t('submit') }}</Button>
      </template>
    </BaseDrawer>
    <!--右侧预览弹窗(新建、查看)-->
    <BaseDrawer
      :title="targetWithFlowModalVisibleSingleTag ? targetWithFlowModalSingleTagTitle : $t('overview')"
      :visible.sync="targetWithFlowModalVisible"
      realWidth="70%"
      :scrollable="true"
      :maskClosable="false"
    >
      <template slot-scope="{maxHeight}" slot="content">
        <Table
          border
          :columns="targetWithFlowModelColums"
          :max-height="maxHeight"
          :data="modelDataWithFlowNodes"
          :span-method="modelDataHandleSpan"
        >
        </Table>
      </template>
    </BaseDrawer>
    <!--编排执行弹窗(查看)-->
    <BaseDrawer
      :title="$t('select_an_operation')"
      :visible.sync="workflowActionModalVisible"
      v-if="workflowActionModalVisible"
      :realWidth="1200"
      :scrollable="true"
      :maskClosable="false"
    >
      <template slot="content">
        <!--节点操作-->
        <BaseHeaderTitle :title="$t('fe_nodeOperate')">
          <div ref="action" style="padding-left: 20px">
            <div
              v-if="
                noActionFlag ||
                  ['start', 'end', 'abnormal'].includes(currentNodeType) ||
                  ['NotStarted', 'Completed', 'InternallyTerminated', 'Faulted'].includes(
                    currentInstanceStatusForNodeOperation
                  )
              "
              class="no-data"
            >
              {{ $t('fe_noOperate') }}
            </div>
            <template v-else>
              <!--高危检测-->
              <Alert v-if="['Risky'].includes(currentNodeStatus)" type="warning" show-icon>{{
                $t('fe_riskyTips')
              }}</Alert>
              <Button
                v-if="['Risky'].includes(currentNodeStatus)"
                style="background-color: #bf22e0; color: white"
                @click="workFlowActionHandler('risky')"
                :loading="btnLoading"
              >{{ $t('dangerous_confirm') }}</Button>
              <!--反选数据-->
              <Button
                type="primary"
                v-if="
                  ['NotStarted', 'Risky'].includes(currentNodeStatus) &&
                    ['human', 'automatic', 'data', 'subProc'].includes(currentNodeType)
                "
                @click="workFlowActionHandler('dataSelection')"
                :loading="btnLoading"
              >{{ $t('data_selection') }}</Button>
              <!--节点重试-->
              <Button
                type="primary"
                v-if="['Faulted', 'Timeouted'].includes(currentNodeStatus)"
                @click="workFlowActionHandler('partialRetry')"
                :loading="btnLoading"
              >{{ $t('partial_retry') }}</Button>
              <!--跳过节点-->
              <Button
                type="warning"
                v-if="['Faulted', 'Timeouted', 'Risky'].includes(currentNodeStatus)"
                @click="workFlowActionHandler('skip')"
                :loading="btnLoading"
                style="margin-left: 10px"
              >{{ $t('skip') }}</Button>
              <!--时间节点-手动跳过-->
              <div
                v-if="['InProgress'].includes(currentNodeStatus) && ['timeInterval', 'date'].includes(currentNodeType)"
                class="time-node"
              >
                <span>{{ $t('be_expected_completion_time') }}：【{{ manualSkipParams.dateToDisplay }}】</span>
                <div
                  v-if="currentNodeItem.allowContinue"
                  class="workflowActionModal-container"
                  style="margin-top: 10px"
                >
                  <Button @click="confirmSkip" type="warning">{{ $t('be_manual_skip') }}</Button>
                </div>
              </div>
              <!--判断分支-选择分支-->
              <div
                v-if="['InProgress'].includes(currentNodeStatus) && ['decision'].includes(currentNodeType)"
                class="branch-node"
              >
                <div style="width: fit-content; display: inline-block; text-align: left">
                  {{ $t('be_decision_branch') }}：
                </div>
                <Select v-model="manualSkipParams.message" style="width: 350px">
                  <Option v-for="item in manualSkipParams.branchOption" :value="item" :key="item">{{ item }}</Option>
                </Select>
                <div class="workflowActionModal-container" style="margin-top: 10px">
                  <Button type="warning" @click="confirmExecuteBranch" :disabled="!manualSkipParams.message">{{
                    $t('be_execute_branch')
                  }}</Button>
                </div>
              </div>
            </template>
          </div>
        </BaseHeaderTitle>
        <!--插件服务-->
        <BaseHeaderTitle
          v-if="['human', 'automatic'].includes(nodeInstance.nodeType)"
          :title="$t('workflow_plugin_aspect')"
          :defaultExpand="false"
        >
          <PluginService :nodeInstance="nodeInstance" />
        </BaseHeaderTitle>
        <!--子编排-->
        <BaseHeaderTitle
          v-if="['subProc'].includes(nodeInstance.nodeType)"
          :title="$t('child_workflow')"
          :defaultExpand="false"
        >
          <ChildFlow :nodeInstance="nodeInstance" @getSubProcItem="getSubProcItem" />
        </BaseHeaderTitle>
        <!--数据绑定-->
        <BaseHeaderTitle
          v-if="['human', 'automatic', 'data', 'subProc'].includes(nodeInstance.nodeType)"
          :title="$t('dataBinding')"
          :defaultExpand="false"
        >
          <DataBind
            :currentSelectedEntity="flowData.rootEntity"
            :nodeInstance="nodeInstance"
            :subProcItem="subProcItem"
          />
        </BaseHeaderTitle>
        <!--执行记录-->
        <BaseHeaderTitle :title="$t('execution_history')">
          <div
            v-if="['human', 'automatic', 'data', 'subProc'].includes(nodeInstance.nodeType)"
            class="execution-history"
          >
            <Tabs v-model="currentExeHistoryNodeId" type="card">
              <TabPane
                v-for="i in executeHistory"
                :key="i.id"
                :label="`${i.beginTime} / ${i.operator || '-'}`"
                :name="i.id"
              >
                <!--节点信息-->
                <div class="header-title">{{ $t('fe_nodeInfo') }}</div>
                <json-viewer :value="i.nodeDetailResponseHeader || {}" :expand-depth="5"></json-viewer>
                <!--API调用-->
                <div class="header-title" style="padding-top: 20px">{{ $t('fe_apiInfo') }}</div>
                <Table :columns="nodeDetailColumns" tooltip="true" :data="i.nodeDetailIO"></Table>
              </TabPane>
            </Tabs>
          </div>
          <div v-else class="execution-history">
            <!--节点信息-->
            <div class="header-title">{{ $t('fe_nodeInfo') }}</div>
            <json-viewer
              :value="(executeHistory[0] && executeHistory[0].nodeDetailResponseHeader) || {}"
              :expand-depth="5"
            ></json-viewer>
            <!--API调用-->
            <div class="header-title" style="padding-top: 20px">{{ $t('fe_apiInfo') }}</div>
            <Table
              :columns="nodeDetailColumns"
              tooltip="true"
              :data="(executeHistory[0] && executeHistory[0].nodeDetailIO) || []"
            ></Table>
          </div>
        </BaseHeaderTitle>
      </template>
    </BaseDrawer>
    <!--节点重试/反选数据弹窗(查看)-->
    <Modal
      :title="currentNodeTitle"
      v-model="retryTargetModalVisible"
      :scrollable="true"
      :ok-text="$t('submit')"
      class="model_target"
      :width="1100"
      @on-ok="retryTargetModelConfirm"
    >
      <Input
        v-model="retryTableFilterParam"
        :placeholder="$t('bc_execution_instance')"
        style="width: 300px; margin-bottom: 8px"
      />
      <Table
        border
        ref="selection"
        max-height="500"
        @on-select="retrySingleSelect"
        @on-select-cancel="retrySingleCancel"
        @on-select-all-cancel="retrySelectAllCancel"
        @on-select-all="retrySelectAll"
        :columns="retryTargetModelColums.filter(col => !col.disabled)"
        :data="retryTartetModels"
      >
      </Table>
      <div slot="footer">
        <span v-if="isNodeCanBindData" style="font-size: 12px; color: red; margin-right: 8px">{{
          $t('be_dynamic_binding_warning')
        }}</span>
        <Button @click="retryTargetModalVisible = false">{{ $t('cancel') }}</Button>
        <!--只有数据反选，并且为动态绑定节点，禁用提交按钮-->
        <Button
          type="primary"
          :disabled="isNodeCanBindData && !['Faulted', 'Timeouted'].includes(currentNodeStatus)"
          @click="retryTargetModelConfirm"
          style="margin-left:8px;"
        >{{ $t('submit') }}</Button>
      </div>
    </Modal>
    <!--编排执行详情弹窗(新建)-->
    <BaseDrawer
      :title="currentNodeTitle"
      :visible.sync="targetModalVisible"
      v-if="targetModalVisible"
      :realWidth="1200"
      :scrollable="true"
      :maskClosable="false"
    >
      <template slot-scope="{maxHeight}" slot="content">
        <!--插件服务-->
        <BaseHeaderTitle
          v-if="['human', 'automatic'].includes(nodeInstance.nodeType)"
          :title="$t('workflow_plugin_aspect')"
          :defaultExpand="false"
        >
          <PluginService :nodeInstance="nodeInstance" />
        </BaseHeaderTitle>
        <!--子编排-->
        <BaseHeaderTitle
          v-if="['subProc'].includes(nodeInstance.nodeType)"
          :title="$t('child_workflow')"
          :defaultExpand="false"
        >
          <ChildFlow :nodeInstance="nodeInstance" @getSubProcItem="getSubProcItem" />
        </BaseHeaderTitle>
        <!--数据绑定-->
        <BaseHeaderTitle
          v-if="['human', 'automatic', 'data', 'subProc'].includes(nodeInstance.nodeType)"
          :title="$t('dataBinding')"
          :defaultExpand="false"
        >
          <DataBind
            :currentSelectedEntity="flowData.rootEntity"
            :nodeInstance="nodeInstance"
            :subProcItem="subProcItem"
          />
        </BaseHeaderTitle>
        <!--操作对象-->
        <BaseHeaderTitle :title="$t('bc_execution_instance')">
          <Input
            v-model="tableFilterParam"
            :placeholder="$t('please_input') + $t('object')"
            style="width: 400px; margin-bottom: 8px"
          />
          <!-- {{ catchNodeTableList.length }} -->
          <Table
            :border="false"
            size="small"
            ref="selection"
            :max-height="maxHeight - 100"
            @on-select="singleSelect"
            @on-select-cancel="singleCancel"
            @on-select-all-cancel="selectAllCancel"
            @on-select-all="selectAll"
            :columns="targetModelColums.filter(col => !col.disabled)"
            :data="tartetModels"
          >
            <template slot-scope="{row}" slot="action">
              <div style="display: flex; justify-content: space-around">
                <Button type="info" size="small" @click="modelGraphMouseenterHandler(row)">{{
                  $t('view') + $t('object')
                }}</Button>
                <Button
                  v-if="row.nodeType === 'subProc'"
                  type="default"
                  size="small"
                  style="margin-left: 5px"
                  @click="viewSubProcExecution(row)"
                >{{ $t('fe_view_childFlow') }}</Button>
              </div>
            </template>
          </Table>
          <span v-if="isNodeCanBindData" style="font-size: 12px; color: red; margin-right: 8px">{{
            $t('be_dynamic_binding_warning')
          }}</span>
        </BaseHeaderTitle>
      </template>
      <template slot="footer">
        <Button @click="targetModalVisible = false">{{ $t('cancel') }}</Button>
        <Button type="primary" :disabled="isNodeCanBindData" @click="targetModelConfirm(false)" style="margin-left:8px;">{{
          $t('submit')
        }}</Button>
      </template>
    </BaseDrawer>
    <!--对象查看弹框-->
    <Modal v-model="showNodeDetail" :fullscreen="nodeDetailFullscreen" width="1000" :styles="{top: '50px'}">
      <p slot="header">
        <span>{{ nodeTitle }}</span>
        <Icon v-if="!nodeDetailFullscreen" @click="zoomModal" class="header-icon" type="ios-expand" />
        <Icon v-else @click="nodeDetailFullscreen = false" class="header-icon" type="ios-contract" />
      </p>
      <div :style="[{overflow: 'auto', margin: '0 6px 6px'}, fullscreenModalContentStyle]">
        <json-viewer :value="nodeDetail || {}" :expand-depth="5"></json-viewer>
      </div>
    </Modal>
    <!--高危确认二次弹框(查看)-->
    <Modal v-model="confirmModal.isShowConfirmModal" width="1000">
      <div>
        <Icon :size="28" :color="'#f90'" type="md-help-circle" />
        <span class="confirm-msg">{{ $t('confirm_to_exect') }}</span>
      </div>
      <div style="max-height: 390px; overflow: auto">
        <pre style="margin-left: 44px; margin-top: 22px">{{ this.confirmModal.message }}</pre>
      </div>
      <div slot="footer">
        <span style="margin-left: 30px; color: #ff4d4f; float: left; text-align: left">
          <Checkbox v-model="confirmModal.check">{{ $t('dangerous_confirm_tip') }}</Checkbox>
        </span>
        <Button type="text" @click="confirmModal.isShowConfirmModal = false">{{ $t('bc_cancel') }}</Button>
        <Button type="warning" :disabled="!confirmModal.check" @click="confirmToExecution" style="margin-left:8px;">{{
          $t('bc_confirm')
        }}</Button>
      </div>
    </Modal>
    <!--定时执行弹框(查看)-->
    <Modal v-model="timeConfig.isShow" :title="$t('timed_execution')">
      <Form :label-width="100" label-colon>
        <FormItem :label="$t('timing_type')">
          <Select
            v-model="timeConfig.params.scheduleMode"
            @on-change="timeConfig.params.time = '00:00:00'"
            style="width: 95%"
          >
            <Option v-for="item in timeConfig.scheduleModeOptions" :key="item.value" :value="item.value">{{
              item.label
            }}</Option>
          </Select>
        </FormItem>
        <FormItem
          v-if="['Monthly', 'Weekly'].includes(timeConfig.params.scheduleMode)"
          :label="timeConfig.params.scheduleMode === 'Monthly' ? $t('day') : $t('week')"
        >
          <Select v-model="timeConfig.params.cycle" style="width: 95%">
            <Option
              v-for="item in timeConfig.modeToValue[timeConfig.params.scheduleMode]"
              :key="item.value"
              :value="item.value"
            >{{ item.label }}</Option>
          </Select>
        </FormItem>
        <FormItem :label="$t('execute_date')">
          <TimePicker
            :value="timeConfig.params.time"
            @on-change="changeTimePicker"
            style="width: 355px"
            :disabled-hours="
              timeConfig.params.scheduleMode === 'Hourly'
                ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]
                : []
            "
            :clearable="false"
            format="HH:mm:ss"
          ></TimePicker>
        </FormItem>
        <FormItem :label="$t('be_mgmt_role')">
          <Select v-model="timeConfig.params.role" style="width: 370px">
            <Option v-for="item in timeConfig.currentUserRoles" :key="item.name" :value="item.name">{{
              item.displayName
            }}</Option>
          </Select>
        </FormItem>
        <FormItem :label="$t('be_email_push')">
          <Select v-model="timeConfig.params.mailMode" style="width: 370px">
            <Option v-for="item in timeConfig.mailModeOptions" :key="item.value" :value="item.value">{{
              item.label
            }}</Option>
          </Select>
        </FormItem>
      </Form>
      <div slot="footer">
        <Button type="text" @click="timeConfig.isShow = false">{{ $t('bc_cancel') }}</Button>
        <Button type="primary" @click="saveTime" style="margin-left:8px;">{{ $t('save') }}</Button>
      </div>
    </Modal>
    <!--日志查看input列嵌套详情弹框(查看)-->
    <Modal v-model="attrValue.isShow" :title="attrValue.attr">
      <Form :label-width="120" label-colon>
        <FormItem :label="$t('attribute_type')">
          <span style="word-break: break-word">{{ attrValue.data.type }}</span>
        </FormItem>
        <FormItem :label="$t('attribute')">
          <span style="word-break: break-word">{{ attrValue.data.value }}</span>
        </FormItem>
      </Form>
      <div slot="footer">
        <Button type="primary" @click="attrValue.isShow = false">{{ $t('bc_cancel') }}</Button>
      </div>
    </Modal>
    <!-- 非本人编排提示 -->
    <Modal
      :title="$t('be_workflow_non_owner_title')"
      v-model="isShowNonOwnerModal"
      :mask-closable="false"
      :scrollable="true"
      :z-index="10000"
    >
      <span style="margin: 8px">
        {{ $t('be_workflow_non_owner_tip1') }}[{{ flowOwner }}]{{ $t('be_workflow_non_owner_tip2') }}
      </span>
      <div slot="footer">
        <Button type="warning" @click="isShowNonOwnerModal = false">{{ $t('be_i_aware') }}</Button>
      </div>
    </Modal>
  </div>
</template>
<script>
import {
  getAllFlow,
  getFlowOutlineByID,
  // getTargetOptions,
  getTreePreviewData,
  previewFlowExecute,
  createFlowInstance,
  getProcessInstances,
  getProcessInstance,
  retryProcessInstance,
  getModelNodeDetail,
  getNodeBindings,
  getNodeContext,
  getDataByNodeDefIdAndProcessSessionId,
  setDataByNodeDefIdAndProcessSessionId,
  getAllBindingsProcessSessionId,
  getTargetModelByProcessDefId,
  getPreviewEntitiesByInstancesId,
  createWorkflowInstanceTerminationRequest,
  getTaskNodeInstanceExecBindings,
  updateTaskNodeInstanceExecBindings,
  setUserScheduledTasks,
  getMetaData,
  getExecutionTimeByNodeId,
  skipNode,
  getBranchByNodeId,
  executeBranch,
  pauseAndContinueFlow,
  collectFlow,
  unCollectFlow
} from '@/api/server'
import JsonViewer from 'vue-json-viewer'
import * as d3 from 'd3-selection'
// eslint-disable-next-line no-unused-vars
import * as d3Graphviz from 'd3-graphviz'
import { addEvent, removeEvent } from '@/pages/util/event.js'
import { debounce, deepClone } from '@/const/util'
import PluginService from './components/plugin-service.vue'
import DataBind from './components/data-bind.vue'
import ChildFlow from './components/child-flow.vue'
import CustomFlowSelect from './components/custom-flow-select.vue'
export default {
  components: {
    JsonViewer,
    PluginService,
    DataBind,
    ChildFlow,
    CustomFlowSelect
  },
  data() {
    return {
      // 属性值展示
      attrValue: {
        attr: '',
        isShow: false,
        data: {
          type: '',
          value: ''
        }
      },
      currentAction: '',
      allFlowNodesModelData: [],
      selectedFlowNodesModelData: [],
      modelDataWithFlowNodes: [],
      targetWithFlowModalVisible: false, // 数据模型总览弹框
      targetWithFlowModalVisibleSingleTag: false, // 数据模型单节点预览弹框
      targetWithFlowModalSingleTagTitle: this.$t('view'),
      flowNodesWithDataModalVisible: false,
      indexArray: [0],
      flowIndexArray: [0],
      btnLoading: false,
      loading: false,
      intervalLoading: false, // 轮询时加载样式
      currentModelNodeRefs: [],
      showNodeDetail: false,
      nodeDetailFullscreen: false,
      fullscreenModalContentStyle: { 'max-height': '500px' },
      tableMaxHeight: 400,
      nodeTitle: null,
      nodeDetail: null,
      graph: {},
      flowGraph: {},
      modelData: [],
      flowData: {},
      nodeInstance: {}, // 编排节点完整数据(查看插件服务和绑定数据用到)
      currentNodeTitle: null,
      rowContent: null,
      allFlowInstances: [],
      allFlowInstancesLoading: false,
      allFlows: [],
      allTarget: [],
      currentFlowNodeId: '',
      selectedFlowInstance: '', // 选择的编排任务
      selectedFlow: '', // 选的的编排
      selectedFlowObj: {},
      selectedTarget: '',
      showExcution: true,
      isExecuteActive: false,
      isEnqueryPage: false,
      workflowActionModalVisible: false,
      targetModalVisible: false,
      retryTargetModalVisible: false,
      tableFilterParam: null,
      retryTableFilterParam: null,
      tartetModels: [],
      retryTartetModels: [],
      catchTartetModels: [],
      flowNodesWithModelDataColums: [
        // 节点名
        {
          title: this.$t('fe_nodeName'),
          key: 'orderedNo',
          width: 240,
          render: (h, params) => <span>{params.row.orderedNo + ' ' + params.row.nodeName}</span>
        },
        {
          type: 'selection',
          width: 60,
          align: 'center'
        },
        // 操作对象类型
        {
          title: this.$t('be_instance_type'),
          key: 'entity',
          width: 270
        },
        // 操作对象
        {
          title: this.$t('bc_execution_instance'),
          key: 'displayName'
        },
        {
          title: this.$t('table_action'),
          key: 'action',
          width: 210,
          align: 'center',
          render: (h, params) => (
            <div style="display: flex; justify-content: space-around">
              <Button
                type="info"
                size="small"
                onClick={() => {
                  this.modelGraphMouseenterHandler(params.row)
                }}
              >
                {`${this.$t('view')}${this.$t('object')}`}
              </Button>
              {params.row.nodeType === 'subProc' && (
                <Button
                  color="#808695"
                  size="small"
                  onClick={() => {
                    this.viewSubProcExecution(params.row)
                  }}
                >
                  {this.$t('fe_view_childFlow')}
                </Button>
              )}
            </div>
          )
        }
      ],
      targetWithFlowModelColums: [
        // 操作对象类型
        {
          title: this.$t('be_instance_type'),
          key: 'entity'
        },
        // 操作对象
        {
          title: this.$t('bc_execution_instance'),
          key: 'displayName'
        },
        // 任务节点
        {
          title: this.$t('task_node'),
          key: 'nodeTitle',
          width: 240,
          render: (h, params) => {
            const nodes = params.row.nodeTitle.split(';')
            return (
              <div>
                {nodes.map((item, index) => (
                  <div style="margin-bottom:5px" key={index}>
                    {item || '-'}
                  </div>
                ))}
              </div>
            )
          }
        },
        {
          title: this.$t('table_action'),
          key: 'action',
          width: 150,
          align: 'center',
          render: (h, params) => (
            <Button
              type="info"
              size="small"
              onClick={() => {
                this.modelGraphMouseenterHandler(params.row)
              }}
            >
              {`${this.$t('view')}${this.$t('object')}`}
            </Button>
          )
        }
      ],
      retryTargetModelColums: [
        {
          type: 'selection',
          width: 60,
          align: 'center'
        },
        // 操作对象类型
        {
          title: this.$t('be_instance_type'),
          key: 'packageName',
          render: (h, params) => <span>{params.row.packageName + ':' + params.row.entityName}</span>
        },
        // 操作对象
        {
          title: this.$t('bc_execution_instance'),
          key: 'entityDisplayName'
        },
        {
          title: this.$t('status'),
          key: 'status'
        },
        {
          title: this.$t('fe_info'),
          key: 'message',
          render: (h, params) => {
            const data = {
              props: {
                content: params.row.message || '',
                delay: 500,
                placement: 'right',
                'max-width': '350'
              }
            }
            return (
              <Tooltip {...data}>
                <div style="text-overflow: ellipsis;overflow: hidden;white-space: nowrap;">{params.row.message}</div>
              </Tooltip>
            )
          }
        }
      ],
      targetModelColums: [
        {
          type: 'selection',
          width: 60,
          align: 'center'
        },
        // 操作对象类型
        {
          title: this.$t('be_instance_type'),
          key: 'packageName',
          render: (h, params) => <span>{params.row.packageName + ':' + params.row.entityName}</span>
        },
        // 操作对象
        {
          title: this.$t('bc_execution_instance'),
          key: 'displayName'
        },
        {
          title: this.$t('table_action'),
          slot: 'action',
          width: 195,
          align: 'center'
        }
      ],
      nodeDetailColumns: [
        {
          title: this.$t('fe_inputs'),
          key: 'inputs',
          render: (h, params) => {
            const jsonData = params.row.inputs
            return (
              <div style="white-space: nowrap; overflow: auto;">
                {jsonData.map((data, index) => (
                  <div key={index} style="margin-left:5px;">
                    {'{'}
                    {Object.entries(data).map(([key, value]) => (
                      <div style="margin-left:5px;">
                        <Icon
                          type="md-search"
                          onClick={() => this.handleClick(key, value)}
                          style="cursor:pointer;color:#5384ff"
                        />
                        {key}: <span style="color:#42b983;">{value}</span>
                      </div>
                    ))}
                    {'}'}
                  </div>
                ))}
              </div>
            )
          }
        },
        {
          title: this.$t('fe_outputs'),
          key: 'outputs',
          render: (h, params) => {
            const strOutput = params.row.outputs
            const noData = strOutput.every(i => i && Object.keys(i).length === 0)
            if (noData) {
              return <span>-</span>
            }
            return (
              <div style="white-space: nowrap; overflow: auto;">
                {strOutput.map((data, index) => (
                  <div key={index} style="margin-left:5px;">
                    {'{'}
                    {Object.entries(data).map(([key, value]) => (
                      <div style="margin-left:5px;">
                        {key}: <span style="color:#42b983;">{value}</span>
                      </div>
                    ))}
                    {'}'}
                  </div>
                ))}
              </div>
            )
          }
        }
      ],
      nodeDetailResponseHeader: null,
      currentFailedNodeID: '',
      timer: null,
      modelDetailTimer: null,
      flowNodesBindings: [],
      flowDetailTimer: null,
      isLoading: false, // 模型数据加载样式
      catchNodeTableList: [],
      retryCatchNodeTableList: [],
      processSessionId: '',
      allBindingsList: [],
      isShowExect: false, // 模型查询返回，激活执行按钮
      stopSuccess: false,
      confirmModal: {
        isShowConfirmModal: false,
        check: false,
        message: '',
        requestBody: ''
      },
      currentInstanceStatusForNodeOperation: '', // 流程状态
      currentInstanceStatus: true,
      timeConfig: {
        isShow: false,
        params: {
          scheduleMode: 'Monthly',
          time: '00:00:00',
          cycle: '',
          role: '',
          mailMode: 'node'
        },
        scheduleModeOptions: [
          {
            label: this.$t('Hourly'),
            value: 'Hourly'
          },
          {
            label: this.$t('Daily'),
            value: 'Daily'
          },
          {
            label: this.$t('Weekly'),
            value: 'Weekly'
          },
          {
            label: this.$t('Monthly'),
            value: 'Monthly'
          }
        ],
        modeToValue: {
          Monthly: [],
          Weekly: [
            {
              label: this.$t('Mon'),
              value: 1
            },
            {
              label: this.$t('Tue'),
              value: 2
            },
            {
              label: this.$t('Wed'),
              value: 3
            },
            {
              label: this.$t('Thu'),
              value: 4
            },
            {
              label: this.$t('Fri'),
              value: 5
            },
            {
              label: this.$t('Sat'),
              value: 6
            },
            {
              label: this.$t('Sun'),
              value: 7
            }
          ]
        },
        mailModeOptions: [
          {
            label: this.$t('be_role_email'),
            value: 'role'
          },
          {
            label: this.$t('be_user_email'),
            value: 'user'
          },
          {
            label: this.$t('be_not_send'),
            value: 'none'
          }
        ],
        currentUserRoles: []
      },
      pluginInfo: '',
      nodesCannotBindData: [], // 动态绑定的节点集合
      isNodeCanBindData: false, // 节点绑定标识
      hasExecuteBranchVisible: false, // 同一编排在进入只进行一次展示
      manualSkipParams: {
        act: '', // 执行动作
        procInstId: '',
        nodeInstId: '',
        message: '', // 选择执行的分支
        branchOption: [] // 可执行分支
      },
      isShowNonOwnerModal: false, // 查询非本人用户编排提示
      flowOwner: '',
      subProcId: '', // 新建执行预览子编排从链接上传过来的子编排ID
      noActionFlag: false, // 节点无操作按钮标识
      subProcBindParentFlag: true, // 子编排是否绑定主编排标识
      from: this.$route.query.from, // 查看页面来源(create新增页 sub子编排预览 main主编排预览 normal普通执行列表查看 time定时执行列表查看)
      subProc: this.$route.query.subProc, // 是否子编排标识
      subProcItem: {}, // 子编排实例
      executeHistory: [], // 编排执行记录(节点重试可能执行多次)
      currentExeHistoryNodeId: '', // 当前执行记录ID
      onlyMyFlowFlag: false // 仅展示本人编排标识
    }
  },
  computed: {
    currentNodeItem() {
      if (!this.flowData.flowNodes) {
        return {}
      }
      const found = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFailedNodeID) || {}
      return found
    },
    currentNodeStatus() {
      if (!this.flowData.flowNodes) {
        return ''
      }
      const found = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFailedNodeID)
      if (found) {
        return found.status
      }

      return ''
    },
    currentNodeType() {
      if (!this.flowData.flowNodes) {
        return ''
      }
      const found = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFailedNodeID)
      if (found) {
        return found.nodeType
      }

      return ''
    }
  },
  watch: {
    selectedFlowInstance: {
      handler(val, oldVal) {
        if (val !== oldVal) {
          this.stopSuccess = false
          this.currentInstanceStatus = true
        }
      }
    },
    targetModalVisible(val) {
      this.tableFilterParam = null
      if (!val) {
        this.catchNodeTableList = []
      }
    },
    retryTableFilterParam(filter) {
      if (!filter) {
        this.retryTartetModels = this.retryCatchNodeTableList
      } else {
        this.retryTartetModels = this.retryCatchNodeTableList.filter(item => item.entityDisplayName.includes(filter))
      }
      this.retryTartetModels.forEach(tm => {
        tm._checked = false
        this.retryCatchNodeTableList.forEach(cn => {
          if (tm.id === cn.id) {
            tm._checked = true
          }
        })
      })
    },
    tableFilterParam(filter) {
      if (!filter) {
        this.tartetModels = this.catchTartetModels
      } else {
        this.tartetModels = this.catchTartetModels.filter(item => item.displayName.includes(filter))
      }
      this.tartetModels.forEach(tm => {
        tm._checked = false
        this.catchNodeTableList.forEach(cn => {
          if (tm.id === cn.id) {
            tm._checked = true
          }
        })
      })
    },
    nodeDetailFullscreen(tag) {
      tag ? (this.fullscreenModalContentStyle = {}) : (this.fullscreenModalContentStyle['max-height'] = '500px')
    },
    // 节点操作弹窗打开，直接查看日志信息
    workflowActionModalVisible(val) {
      if (val === true) {
        this.pluginInfo = ''
        this.nodeDetailResponseHeader = null
        this.executeHistory = []
        this.currentExeHistoryNodeId = ''
        this.flowGraphMouseenterHandler(this.currentFailedNodeID)
        this.noActionFlag = false
        this.$nextTick(() => {
          if (this.$refs.action.children.length === 0) {
            this.noActionFlag = true
          }
        })
      }
    }
  },
  async mounted() {
    const id = this.$route.query.id || ''
    const templateId = this.$route.query.templateId || ''
    this.subProcId = this.$route.query.subProcId || ''
    const targetId = this.$route.query.targetId || ''
    // 查看执行历史
    if (id) {
      this.querySelectedFlowInstanceId = id
      await this.getProcessInstances()
      this.selectedFlowInstance = id
      this.queryHandler()
    }
    // 选择模板新建执行
    if (templateId) {
      this.selectedFlow = templateId
      this.getAllFlow()
      this.orchestrationSelectHandler()
    }
    // 查看子编排执行(新建执行的时候点击子编排节点跳转进来)
    if (this.subProcId) {
      this.selectedFlow = this.subProcId
      this.orchestrationSelectHandler()
      this.getAllFlow()
      this.selectedTarget = this.$route.query.entityDataId || ''
      this.getTargetOptions()
      this.onTargetSelectHandler()
    }
    if (targetId) {
      this.selectedTarget = targetId
      this.getTargetOptions()
      this.onTargetSelectHandler()
    }
  },
  destroyed() {
    clearInterval(this.timer)
    localStorage.removeItem('history-execution-search-params')
  },
  methods: {
    handleBack() {
      if (this.isEnqueryPage) {
        if (['create', 'main', 'sub', 'normal'].includes(this.$route.query.from)) {
          return this.$router.push({
            path: '/implementation/workflow-execution/normal-history',
            query: {
              subProc: this.subProc
            }
          })
        } else if (this.$route.query.from === 'time') {
          return this.$router.push({
            path: '/implementation/workflow-execution/time-history',
            query: {
              subProc: this.subProc
            }
          })
        }
      }
      if (!this.isEnqueryPage) {
        return this.$router.push({
          path: '/implementation/workflow-execution/normal-template',
          query: {
            subProc: this.subProc
          }
        })
      }
    },
    // 收藏or取消收藏
    handleStar: debounce(async function ({ procDefId, collected }) {
      const method = collected ? unCollectFlow : collectFlow
      const params = {
        procDefId
      }
      const { status } = await method(params)
      if (status === 'OK') {
        this.$Notice.success({
          title: this.$t('successful'),
          desc: this.$t('successful')
        })
        this.selectedFlowObj.collected = !collected
      }
    }, 300),
    // 【新建执行】子编排节点支持跳转预览子编排详情
    viewSubProcExecution(row) {
      window.sessionStorage.currentPath = '' // 先清空session缓存页面，不然打开新标签页面会回退到缓存的页面
      const path = `${window.location.origin}/#/implementation/workflow-execution/normal-create?subProcId=${row.subProcDefId}&entityDataId=${row.entityDataId}&sessionId=${row.subPreviewSessionId}`
      window.open(path, '_blank')
    },
    // 【执行详情】子编排调用API列表支持跳转预览子编排详情
    viewSubProcExecutionDetail(id) {
      window.sessionStorage.currentPath = '' // 先清空session缓存页面，不然打开新标签页面会回退到缓存的页面
      const path = `${window.location.origin}/#/implementation/workflow-execution/view-execution?id=${id}&from=sub`
      window.open(path, '_blank')
    },
    // 查看日志调用API详情
    async handleClick(key) {
      this.attrValue.attr = key
      const params = {
        paramName: key,
        serviceId: this.pluginInfo
      }
      const { status, data } = await getMetaData(params)
      if (status === 'OK') {
        this.attrValue.data.type = data.mappingType
        switch (data.mappingType) {
          case 'system_variable':
            this.attrValue.data.value = data.mappingSystemVariableName
            break
          case 'context':
            this.attrValue.data.value = 'N/A'
            break
          case 'constant':
            this.attrValue.data.value = data.mappingValue
            break
          case 'entity':
            this.attrValue.data.value = data.mappingEntityExpression
            break
          default:
            this.attrValue.data.value = ''
        }
        this.attrValue.isShow = true
      }
    },
    changeTimePicker(time) {
      this.timeConfig.params.time = time
    },
    async saveTime() {
      const found = this.allFlowInstances.find(_ => _.id === this.selectedFlowInstance)
      let scheduleExpr = ''
      if (['Hourly', 'Daily'].includes(this.timeConfig.params.scheduleMode)) {
        scheduleExpr = this.timeConfig.params.time
        if (this.timeConfig.params.scheduleMode === 'Hourly') {
          scheduleExpr = this.timeConfig.params.time.substring(3)
        }
      } else {
        scheduleExpr = this.timeConfig.params.cycle + ' ' + this.timeConfig.params.time
      }
      const params = {
        scheduleMode: this.timeConfig.params.scheduleMode,
        scheduleExpr,
        procDefName: found.procInstName,
        procDefId: found.procDefId,
        entityDataName: found.entityDisplayName,
        entityDataId: found.entityDataId,
        mailMode: this.timeConfig.params.mailMode,
        role: this.timeConfig.params.role
      }
      const { status } = await setUserScheduledTasks(params)
      if (status === 'OK') {
        this.$Notice.success({
          title: 'Success',
          desc: 'Success'
        })
        this.timeConfig.isShow = false
      }
    },
    // async setTimedExecution () {
    //   await this.getCurrentUserRoles()
    //   this.timeConfig.params.scheduleMode = 'Monthly'
    //   this.timeConfig.params.time = '00:00:00'
    //   this.timeConfig.params.cycle = ''
    //   this.timeConfig.params.role = ''
    //   this.timeConfig.params.mailMode = 'none'
    //   this.timeConfig.isShow = true
    // },
    // async getCurrentUserRoles () {
    //   const { status, data } = await getCurrentUserRoles()
    //   if (status === 'OK') {
    //     this.timeConfig.currentUserRoles = data
    //   }
    // },
    async getDetail(row) {
      if (!row.packageName || !row.entityName || !row.dataId) {
        return
      }
      const params = {
        additionalFilters: [
          {
            attrName: 'id',
            op: 'eq',
            condition: row.dataId
          }
        ]
      }
      const { status, data } = await getModelNodeDetail(row.packageName, row.entityName, params)
      if (status === 'OK') {
        this.rowContent = data
      }
    },
    async retryTargetModelConfirm() {
      const found = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFailedNodeID)
      const tem = []
      this.retryTartetModels.forEach(d => {
        const f = this.retryCatchNodeTableList.find(c => c.id === d.id)
        tem.push({
          ...d,
          bound: f.bound,
          confirmToken: f.confirmToken
        })
      })
      const payload = {
        nodeInstId: found.id,
        procInstId: found.procInstId,
        data: tem
      }
      const { status } = await updateTaskNodeInstanceExecBindings(payload)
      if (status === 'OK') {
        if (this.currentAction === 'dataSelection') {
          this.$Notice.success({
            title: 'Success',
            desc: 'Success'
          })
          this.workflowActionModalVisible = false
          this.retryTargetModalVisible = false
          return
        }
        const retry = await retryProcessInstance({
          act: 'retry',
          nodeInstId: found.id,
          procInstId: found.procInstId
        })
        if (retry.status === 'OK') {
          this.$Notice.success({
            title: 'Success',
            desc: 'Retry' + ' action is proceed successfully'
          })
          this.workflowActionModalVisible = false
          this.retryTargetModalVisible = false
          this.processInstance()
        }
      }
    },
    async targetModelConfirm(visible) {
      // TODO:
      this.targetModalVisible = visible
      if (!visible) {
        await this.updateNodeInfo()
        this.formatRefNodeIds()
        this.renderModelGraph()
      }
    },
    singleSelect(selection, row) {
      this.catchNodeTableList = this.catchNodeTableList.concat(row)
    },
    singleCancel(selection, row) {
      const index = this.catchNodeTableList.findIndex(cn => cn.id === row.id)
      this.catchNodeTableList.splice(index, 1)
    },
    selectAll(selection) {
      const temp = []
      this.catchNodeTableList.forEach(cntl => {
        temp.push(cntl.id)
      })
      selection.forEach(se => {
        if (!temp.includes(se.id)) {
          this.catchNodeTableList.push(se)
        }
      })
    },
    selectAllCancel() {
      const temp = []
      this.tartetModels.forEach(tm => {
        temp.push(tm.id)
      })
      if (this.tableFilterParam) {
        this.catchNodeTableList = this.catchNodeTableList.filter(item => !temp.includes(item.id))
      } else {
        this.catchNodeTableList = []
      }
    },
    retrySingleSelect(selection, row) {
      const find = this.retryCatchNodeTableList.find(item => item.id === row.id)
      find.bound = 'Y'
    },
    retrySingleCancel(selection, row) {
      const find = this.retryCatchNodeTableList.find(cn => cn.id === row.id)
      find.bound = 'N'
    },
    retrySelectAll() {
      this.retryCatchNodeTableList.forEach(item => {
        item.bound = 'Y'
      })
    },
    retrySelectAllCancel() {
      this.retryCatchNodeTableList.forEach(item => {
        item.bound = 'N'
      })
    },
    // 左侧编排预览勾选数据
    allFlowNodesSelectChange(selection) {
      this.selectedFlowNodesModelData = selection
    },
    async setFlowDataForAllNodes() {
      const compare = (a, b) => {
        if (a.orderedNo * 1 < b.orderedNo * 1) {
          return -1
        }
        if (a.orderedNo * 1 > b.orderedNo * 1) {
          return 1
        }
        return 0
      }
      const allPromises = []
      this.flowData.flowNodes
        .filter(_ => _.orderedNo)
        .forEach(node => {
          allPromises.push(getDataByNodeDefIdAndProcessSessionId(node.nodeDefId, this.processSessionId))
        })
      const dataArray = await Promise.all(allPromises)
      this.selectedFlowNodesModelData = []
      this.allFlowNodesModelData = []
        .concat(
          ...dataArray.map(_ =>
            _.data.map(d => {
              const found = this.modelData.find(j => j.dataId === d.entityDataId)
              const flowNode = this.flowData.flowNodes.find(j => j.orderedNo === d.orderedNo)
              const res = {
                ...d,
                _checked: d.bound === 'Y',
                _disabled: this.nodesCannotBindData.includes(flowNode.nodeDefId), // 动态绑定节点不支持修改
                ...found,
                entity: found.packageName + ':' + found.entityName,
                nodeName: flowNode.nodeName,
                nodeDefId: flowNode.nodeDefId,
                nodeType: flowNode.nodeType
              }
              if (d.bound === 'Y') {
                this.selectedFlowNodesModelData.push(res)
              }
              return res
            }))
        )
        .sort(compare)
      let start = 0
      this.flowIndexArray = [0]
      for (let i = 0; i < this.allFlowNodesModelData.length; i++) {
        const startName = this.allFlowNodesModelData[start].orderedNo + this.allFlowNodesModelData[start].nodeName
        const node = this.allFlowNodesModelData[i]
        if (node.orderedNo + node.nodeName !== startName) {
          start = i
          this.flowIndexArray.push(i)
        }
      }
      this.flowNodesWithDataModalVisible = true
    },
    flowNodeDataHandleSpan({ rowIndex, columnIndex }) {
      return this.rowSpanComputed(this.allFlowNodesModelData, this.flowIndexArray, rowIndex, columnIndex)
    },
    modelDataHandleSpan({ rowIndex, columnIndex }) {
      return this.rowSpanComputed(this.modelDataWithFlowNodes, this.indexArray, rowIndex, columnIndex)
    },
    rowSpanComputed(data, indexArray, rowIndex, columnIndex) {
      let arr = []
      for (let i = 0; i < indexArray.length; i++) {
        if (rowIndex === indexArray[i] && columnIndex === 0) {
          arr = [indexArray[i + 1] - indexArray[i], 1]
        } else if (rowIndex > indexArray[i - 1] && rowIndex < indexArray[i] && columnIndex === 0) {
          arr = [0, 0]
        }
      }
      if (rowIndex === indexArray[indexArray.length - 1] && columnIndex === 0) {
        arr = [data.length - indexArray[indexArray.length - 1], 1]
      }
      if (rowIndex > indexArray[indexArray.length - 1] && columnIndex === 0) {
        arr = [0, 0]
      }
      return arr
    },
    showModelDataWithFlow() {
      this.modelDataWithFlowNodes = this.modelData.map(_ => ({
        ..._,
        entity: _.packageName + ':' + _.entityName,
        nodeTitle:
          _.refFlowNodeIds.length > 0
            ? _.refFlowNodeIds
              .map(id => {
                const found = this.flowData.flowNodes.find(n => n.orderedNo === id)
                return found.orderedNo + ' ' + found.nodeName
              })
              .join(';')
            : ''
      }))
      this.targetWithFlowModalVisibleSingleTag = false
      this.targetWithFlowModalVisible = true
      let start = 0
      this.indexArray = [0]
      for (let i = 0; i < this.modelDataWithFlowNodes.length; i++) {
        const startEntity = this.modelDataWithFlowNodes[start].entity
        if (this.modelDataWithFlowNodes[i].entity !== startEntity) {
          start = i
          this.indexArray.push(i)
        }
      }
    },
    async flowNodesTargetModelConfirm() {
      const obj = {}
      const selectObj = {}
      // 表格数据按节点分组
      const tableAllFlowNodesModelData = deepClone(this.allFlowNodesModelData)
      tableAllFlowNodesModelData.forEach(_ => {
        if (!obj[_.nodeDefId]) {
          obj[_.nodeDefId] = []
          obj[_.nodeDefId].push(_)
        } else {
          obj[_.nodeDefId].push(_)
        }
      })
      // 表格勾选数据按节点分组
      this.selectedFlowNodesModelData.forEach(_ => {
        if (!selectObj[_.nodeDefId]) {
          selectObj[_.nodeDefId] = []
          selectObj[_.nodeDefId].push(_)
        } else {
          selectObj[_.nodeDefId].push(_)
        }
      })
      const promiseArray = []
      Object.keys(obj).forEach(key => {
        // 解决有些勾上的数据bound为N的bug
        obj[key].forEach(item => {
          const selectNodeDefIds = (selectObj[key] && selectObj[key].map(i => i.id)) || []
          if (selectNodeDefIds.includes(item.id)) {
            item.bound = 'Y'
          } else {
            item.bound = 'N'
          }
        })
        promiseArray.push(setDataByNodeDefIdAndProcessSessionId(key, this.processSessionId, obj[key]))
      })
      await Promise.all(promiseArray)
      this.$Notice.success({
        title: 'Success',
        desc: 'Success'
      })
      this.flowNodesWithDataModalVisible = false
    },
    async updateNodeInfo() {
      const currentNode = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFlowNodeId)
      const payload = this.catchNodeTableList.map(_ => ({
        ..._,
        bound: 'Y'
      }))
      await setDataByNodeDefIdAndProcessSessionId(currentNode.nodeDefId, this.processSessionId, payload)
      const filter = this.allBindingsList.filter(_ => _.nodeDefId !== currentNode.nodeDefId)
      this.$Notice.success({
        title: 'Success',
        desc: 'Success'
      })
      this.allBindingsList = filter.concat(payload)
      this.targetModalVisible = false
    },
    // 获取执行记录列表
    async getProcessInstances(form = {}) {
      const params = {
        params: {
          withCronIns: this.from === 'normal' ? 'no' : this.from === 'time' ? 'yes' : '',
          search: form.search || '',
          withSubProc: '',
          mgmtRole: '',
          createdBy: form.onlyShowMyFlow ? localStorage.getItem('username') : ''
        }
      }
      this.allFlowInstancesLoading = true
      const { status, data } = await getProcessInstances(params)
      this.allFlowInstancesLoading = false
      if (status === 'OK') {
        this.allFlowInstances = data || []
        const id = this.$route.query.id
        const hasFlag = this.allFlowInstances.some(i => i.id === id)
        // 没有这条记录数据，则根据ID查询拼接起来
        if (!hasFlag && Object.keys(form).length === 0) {
          params.params.search = id
          const { status, data } = await getProcessInstances(params)
          if (status === 'OK' && data && data[0]) {
            this.allFlowInstances.unshift(data[0])
          }
        }
      }
    },
    // 刷新当前选中执行记录状态
    async fetchCurrentInstanceStatus() {
      const params = {
        params: {
          withCronIns: this.from === 'normal' ? 'no' : this.from === 'time' ? 'yes' : '',
          search: this.$route.query.id,
          withSubProc: '',
          mgmtRole: ''
        }
      }
      const { status, data } = await getProcessInstances(params)
      if (status === 'OK' && data && data[0]) {
        const index = this.allFlowInstances.findIndex(i => i.id === data[0].id)
        this.allFlowInstances.splice(index, 1, data[0])
      }
    },
    async getNodeBindings(id) {
      if (!id) {
        return
      }
      const { status, data } = await getNodeBindings(id)
      if (status === 'OK') {
        this.flowNodesBindings = data
      }
    },
    async getAllFlow() {
      const { status, data } = await getAllFlow(false)
      if (status === 'OK') {
        this.allFlows = data.sort((a, b) => {
          const s = a.createdTime.toLowerCase()
          const t = b.createdTime.toLowerCase()
          if (s > t) {
            return -1
          }
          if (s < t) {
            return 1
          }
        })
        this.selectedFlowObj = this.allFlows.find(i => i.procDefId === this.selectedFlow) || {}
      }
    },
    clearFlow() {
      d3.select('#flow').selectAll('*')
        .remove()
      this.clearTarget()
    },
    orchestrationSelectHandler() {
      this.currentFlowNodeId = ''
      this.currentModelNodeRefs = []
      this.getFlowOutlineData(this.selectedFlow)
      this.selectedFlowObj = this.allFlows.find(i => i.procDefId === this.selectedFlow) || {}
      if (this.selectedFlow && this.isEnqueryPage === false) {
        this.showExcution = true
        this.selectedTarget = ''
        this.modelData = []
        this.formatRefNodeIds()
        this.renderModelGraph()
      }
    },
    async getTargetOptions() {
      if (!(this.selectedFlow && this.selectedFlow.length > 0)) {
        return
      }
      const { status, data } = await getTargetModelByProcessDefId(this.selectedFlow)
      if (status === 'OK') {
        this.allTarget = data
      }
    },
    clearHistoryOrch() {
      this.stop()
      this.selectedFlow = ''
      this.selectedTarget = ''
      this.currentInstanceStatusForNodeOperation = ''
      d3.select('#flow').selectAll('*')
        .remove()
      d3.select('#graph').selectAll('*')
        .remove()
    },
    getCurrentInstanceStatus() {
      const found = this.allFlowInstances.find(_ => _.id === this.selectedFlowInstance)
      if (found && ['Completed', 'InternallyTerminated', 'Faulted'].includes(found.status)) {
        this.currentInstanceStatus = true
      } else {
        this.currentInstanceStatus = false
      }
    },
    queryHandler() {
      this.hasExecuteBranchVisible = false
      this.currentInstanceStatusForNodeOperation = ''
      this.stop()
      if (!this.selectedFlowInstance) {
        return
      }
      this.getStatus()
      this.getCurrentInstanceStatus()
      this.isEnqueryPage = true
      this.$nextTick(async () => {
        const found = this.allFlowInstances.find(_ => _.id === this.selectedFlowInstance)
        if (!(found && found.id)) {
          return
        }
        this.currentInstanceStatusForNodeOperation = found.status
        this.selectedFlow = found.procDefId
        this.selectedTarget = found.entityDataId
        this.processInstance()
        this.getNodeBindings(found.id)
        const { status, data } = await getProcessInstance(found.id)
        if (status === 'OK') {
          this.flowData = {
            ...data,
            flowNodes: data.taskNodeInstances
          }
          // this.getTargetOptions()
          removeEvent('.retry', 'click', this.retryHandler)
          removeEvent('.normal', 'click', this.normalHandler)
          removeEvent('.time-node', 'click', this.timeNodeHandler)
          removeEvent('.decision-node', 'click', this.executeBranchHandler)
          this.initFlowGraph(true)
          this.showExcution = false
          this.nodesCannotBindData = data.taskNodeInstances
            .filter(
              d => [1, 2].includes(d.dynamicBind) && ['NotStarted', 'Risky', 'Faulted', 'Timeouted'].includes(d.status)
            )
            .map(d => d.nodeId)
          this.subProcBindParentFlag = Boolean(data.parentProcIns && data.parentProcIns.procInsId)
        }
        this.getModelData()
        this.tipForNonOwner(found)
      })
    },
    tipForNonOwner(flow) {
      if (
        ['InProgress', 'Timeouted', 'Stop'].includes(flow.status)
        && flow.operator !== localStorage.getItem('username')
      ) {
        this.flowOwner = flow.operator
        this.isShowNonOwnerModal = true
      }
    },
    // queryHistory() {
    //   this.selectedTarget = null
    //   this.stop()
    //   this.isEnqueryPage = true
    //   this.showExcution = false
    //   this.selectedFlow = ''
    //   this.modelData = []
    //   this.flowData = {}
    //   this.$nextTick(() => {
    //     this.initModelGraph()
    //     this.initFlowGraph()
    //   })
    // },
    // createHandler() {
    //   this.selectedTarget = null
    //   this.stop()
    //   this.isEnqueryPage = false
    //   this.selectedFlowInstance = ''
    //   this.selectedFlow = ''
    //   this.modelData = []
    //   this.flowData = {}
    //   this.showExcution = false
    //   this.$nextTick(() => {
    //     this.initModelGraph()
    //     this.initFlowGraph()
    //   })
    // },
    clearTarget() {
      this.isExecuteActive = false
      this.showExcution = false
      this.selectedTarget = ''
      d3.select('#graph').selectAll('*')
        .remove()
    },
    onTargetSelectHandler() {
      this.isShowExect = false
      this.showExcution = true
      this.processSessionId = ''
      if (!this.selectedTarget) {
        return
      }
      this.currentModelNodeRefs = []
      this.getModelData()
    },
    async getModelData() {
      this.modelData = []
      if ((!this.selectedFlow || !this.selectedTarget) && !this.isEnqueryPage) {
        this.renderModelGraph()
        return
      }
      this.isLoading = true
      const { status, data } = this.isEnqueryPage
        ? await getPreviewEntitiesByInstancesId(this.selectedFlowInstance)
        : await getTreePreviewData(this.selectedFlow, this.selectedTarget, this.$route.query.sessionId || '')
      this.isLoading = false
      if (!this.selectedTarget && !this.isEnqueryPage) {
        return
      }
      if (status === 'OK') {
        if (!this.isEnqueryPage) {
          this.isShowExect = true
        }
        if (data.processSessionId) {
          this.processSessionId = data.processSessionId
          const binds = await getAllBindingsProcessSessionId(data.processSessionId)
          this.allBindingsList = binds.data
        }
        this.modelData = data.entityTreeNodes.map(_ => ({
          ..._,
          refFlowNodeIds: []
        }))
        this.formatRefNodeIds()
      }
      this.renderModelGraph()
    },
    async getFlowOutlineData(id) {
      if (!id) {
        return
      }
      const { status, data } = await getFlowOutlineByID(id)
      if (status === 'OK') {
        this.flowData = data
        this.initFlowGraph()
        this.getTargetOptions()
        this.nodesCannotBindData = data.flowNodes.filter(d => d.dynamicBind === 'Y').map(d => d.nodeId)
      }
    },
    async getOutlineNodeInstance(id, node) {
      const { status, data } = await getFlowOutlineByID(id)
      if (status === 'OK') {
        this.nodeInstance = data.flowNodes.find(item => item.nodeId === node.nodeId)
      }
    },
    formatRefNodeIds() {
      this.modelData.forEach(i => {
        i.refFlowNodeIds = []
        this.allBindingsList.forEach(j => {
          if (j.entityTypeId + ':' + j.entityDataId === i.id) {
            i.refFlowNodeIds.push(j.orderedNo)
          }
        })
      })
    },
    // 绘制数据模型图
    renderModelGraph() {
      const nodes = this.modelData.map(_ => {
        const nodeId = _.id
        // '-' 在viz.js中存在渲染问题
        const nodeTitle = '"' + nodeId.replace(/-/g, '_') + '"'
        const color = _.isHighlight ? '#5DB400' : 'black'
        // const isRecord = _.refFlowNodeIds.length > 0
        // const shape = isRecord ? 'ellipse' : 'ellipse'
        let fillcolor = 'white'
        _.refFlowNodeIds.sort((a, b) => {
          if (a * 1 < b * 1) {
            return -1
          }
          if (a * 1 > b * 1) {
            return 1
          }
          return 0
        })
        const refNodes = []
        let completedNodes = []
        _.refFlowNodeIds.forEach(id => {
          const node = this.flowData.flowNodes.find(_ => _.orderedNo === id)
          refNodes.push(node)
        })
        completedNodes = refNodes.filter(_ => _.status === 'Completed')
        const completedNodesLen = completedNodes.length
        const refNodesLen = refNodes.length
        if (completedNodesLen === refNodesLen && refNodesLen !== 0) {
          fillcolor = '#5DB400'
        }

        if (completedNodesLen > 0 && completedNodesLen < refNodesLen) {
          fillcolor = '#3C83F8'
        }
        const str = _.displayName || _.id
        const refStr = _.refFlowNodeIds.toString().replace(/,/g, '/')
        // const len = refStr.length - _.displayName.length > 0 ? refStr.length : _.displayName.length
        const firstLabel = str.length > 30 ? `${str.slice(0, 1)}...${str.slice(-29)}` : str
        // const fontSize = Math.min((58 / len) * 3, 16)
        const label = firstLabel + '\n' + refStr
        return `${nodeTitle} [label="${label}" class="model" id="${nodeId}" flowInstanceId="" color="${color}" fontsize="6" style="filled" fillcolor="${fillcolor}" shape="box"]`
      })
      const genEdge = () => {
        const pathAry = []

        this.modelData.forEach(_ => {
          if (_.succeedingIds.length > 0) {
            const nodeId = _.id
            let current = []
            current = _.succeedingIds.map(to => '"' + nodeId + '"' + ' -> ' + '"' + to + '"')
            pathAry.push(current)
          }
        })
        return pathAry.flat().toString()
          .replace(/,/g, ';')
      }
      const nodesToString = Array.isArray(nodes) && nodes.length > 0 ? nodes.toString().replace(/,/g, ';') + ';' : ''
      const nodesString = 'digraph G { '
        + 'splines="polyline";'
        + 'bgcolor="transparent";'
        + 'Node [fontname=Arial, shape="ellipse", color="#505a68"];'
        + 'Edge [fontname=Arial, minlen="1", color="#505a68", fontsize=10];'
        + nodesToString
        + genEdge()
        + '}'
      this.reloadGraph()
      this.graph.graphviz.renderDot(nodesString)
      // .on('end', this.setFontSizeForText)
      removeEvent('.model text', 'click', this.modelGraphClickHandler)
      removeEvent('#graph svg', 'click', this.resetcurrentModelNodeRefs)
      addEvent('.model text', 'click', this.modelGraphClickHandler)
      addEvent('#graph svg', 'click', this.resetcurrentModelNodeRefs)
    },
    setFontSizeForText() {
      const nondes = d3.selectAll('#graph svg g .node')._groups[0]
      for (let i = 0; i < nondes.length; i++) {
        const len = nondes[i].children[2].innerHTML.replace(/&nbsp;/g, '').length
        const fontsize = Math.min((nondes[i].children[1].rx.baseVal.value / len) * 3, 16)
        for (let j = 2; j < nondes[i].children.length; j++) {
          nondes[i].children[j].setAttribute('font-size', fontsize)
        }
      }
    },
    resetcurrentModelNodeRefs() {
      if (!this.isEnqueryPage) {
        this.currentModelNodeRefs = []
        this.renderFlowGraph()
      }
    },
    // 点击数据模型节点事件
    modelGraphClickHandler(e) {
      e.preventDefault()
      e.stopPropagation()
      if (!this.isEnqueryPage) {
        const refEle = e.target.parentNode.children[3]
        if (refEle) {
          // 有关联节点时，高亮左边编排图
          this.currentModelNodeRefs = refEle.innerHTML.trim().split('/')
          this.renderFlowGraph()
        } else {
          // 没有关联节点时，左边编排图取消高亮效果
          this.currentModelNodeRefs = []
          this.renderFlowGraph()
        }
      }
      // 弹出节点数据弹框
      this.modelDataWithFlowNodes = this.modelData.map(_ => ({
        ..._,
        entity: _.packageName + ':' + _.entityName,
        nodeTitle:
          _.refFlowNodeIds.length > 0
            ? _.refFlowNodeIds
              .map(id => {
                const found = this.flowData.flowNodes.find(n => n.orderedNo === id)
                return found.orderedNo + ' ' + found.nodeName
              })
              .join(';')
            : ''
      }))
      this.modelDataWithFlowNodes = this.modelDataWithFlowNodes.filter(
        i => i.id === e.target.__data__.parent.attributes.id
      )
      this.targetWithFlowModalVisibleSingleTag = true
      this.targetWithFlowModalVisible = true
    },
    // 查看对象弹框
    modelGraphMouseenterHandler(row) {
      clearTimeout(this.modelDetailTimer)
      this.modelDetailTimer = setTimeout(async () => {
        this.nodeTitle = `${row.displayName}`
        const params = {
          additionalFilters: [
            {
              attrName: 'id',
              op: 'eq',
              condition: row.dataId || row.id
            }
          ],
          procInstId: this.selectedFlowInstance + ''
        }
        const { status, data } = await getModelNodeDetail(row.packageName, row.entityName, params)
        if (status === 'OK') {
          // this.nodeDetail = JSON.stringify(data)
          //   .split(',')
          //   .join(',<br/>')
          this.nodeDetail = data
        }
        this.nodeDetailFullscreen = false
        this.showNodeDetail = true
        this.nodeDetailFullscreen = false
      }, 0)
    },
    ResetFlow() {
      if (this.flowGraph.graphviz) {
        this.flowGraph.graphviz.resetZoom()
      }
    },
    ResetModel() {
      if (this.graph.graphviz) {
        this.graph.graphviz.resetZoom()
      }
    },
    // 绘制编排流程图
    renderFlowGraph(excution) {
      // 节点颜色
      const statusColor = {
        Completed: '#5DB400',
        deployed: '#7F8A96',
        InProgress: '#3C83F8',
        Faulted: '#FF6262',
        Risky: '#BF22E0',
        Timeouted: '#F7B500',
        NotStarted: '#7F8A96',
        wait: '#7F8A96'
      }
      const nodes = this.flowData
        && this.flowData.flowNodes
        && this.flowData.flowNodes
          .filter(i => i.status !== 'predeploy')
          .map(_ => {
            const shapeMap = {
              start: 'circle', // 开始
              end: 'doublecircle', // 结束
              abnormal: 'doublecircle', // 异常
              decision: 'diamond', // 判断开始
              decisionMerge: 'diamond', // 判断结束
              fork: 'Mdiamond', // 并行开始
              merge: 'Mdiamond', // 并行结束
              human: 'tab', // 人工
              automatic: 'rect', // 自动
              data: 'cylinder', // 数据
              subProc: 'component', // 子编排
              date: 'Mcircle', // 固定时间
              timeInterval: 'Mcircle' // 时间间隔
            }
            if (['start', 'end', 'abnormal', 'date', 'timeInterval'].includes(_.nodeType)) {
              const className = 'retry'
              const defaultLabel = _.nodeType
              return `${_.nodeId} [label="${
                _.nodeName || defaultLabel
              }", width="0.8", class="flow ${className}", fixedsize=true, style="${
                excution ? 'filled' : 'none'
              }" fillcolor="${excution ? statusColor[_.status] || '#7F8A96' : '#7F8A96'}" shape="${
                shapeMap[_.nodeType]
              }", id="${_.nodeId}"]`
            }
            // const className = _.status === 'Faulted' || _.status === 'Timeouted' ? 'retry' : 'normal'
            let className = 'retry'
            if (['decision'].includes(_.nodeType) && _.status === 'Faulted') {
              className = ''
            }
            const isModelClick = this.currentModelNodeRefs.indexOf(_.orderedNo) > -1
            return `${_.nodeId} [fixedsize=false label="${
              (_.orderedNo ? _.orderedNo + ' ' : '') + _.nodeName
            }" class="flow ${className}" style="${excution || isModelClick ? 'filled' : 'none'}" fillcolor="${
              excution
                ? statusColor[_.status] || '#7F8A96'
                : isModelClick
                  ? '#f29360'
                  : _.nodeId === this.currentFlowNodeId
                    ? '#5DB400'
                    : '#7F8A96'
            }"  shape="${shapeMap[_.nodeType]}" id="${_.nodeId}" ]`
          })
      const genEdge = () => {
        const lineName = {}
        this.flowData.nodeLinks
          && this.flowData.nodeLinks.forEach(link => {
            lineName[link.source + link.target] = link.name
          })
        const pathAry = []
        this.flowData
          && this.flowData.flowNodes
          && this.flowData.flowNodes.forEach(_ => {
            if (_.succeedingNodeIds.length > 0) {
              let current = []
              current = _.succeedingNodeIds.map(to => {
                const toNodeItem = this.flowData.flowNodes.find(i => i.nodeId === to) || {}
                const edgeColor = statusColor[toNodeItem.status] || '#505a68'
                // 修复判断分支多连线不能区分颜色问题
                if (_.nodeType === 'decision') {
                  return (
                    '"'
                    + _.nodeId
                    + '"'
                    + ' -> '
                    + `${'"' + to + '"'} [label="${lineName[_.nodeId + to]}" color="${edgeColor}" ]`
                  )
                }
                return (
                  '"'
                  + _.nodeId
                  + '"'
                  + ' -> '
                  + `${'"' + to + '"'} [label="${lineName[_.nodeId + to]}" color="${
                    excution ? statusColor[_.status] : 'black'
                  }"]`
                )
              })
              pathAry.push(current)
            }
          })
        return pathAry.flat().toString()
          .replace(/,/g, ';')
      }
      const nodesToString = Array.isArray(nodes) ? nodes.toString().replace(/,/g, ';') + ';' : ''
      const nodesString = 'digraph G {'
        + 'bgcolor="transparent";'
        + 'splines="polyline"'
        + 'Node [fontname=Arial, width=1.8, height=0.45, color="#505a68", fontsize=12]'
        + 'Edge [fontname=Arial, color="#505a68", fontsize=10];'
        + nodesToString
        + genEdge()
        + '}'
      this.flowGraph.graphviz
        .transition()
        .renderDot(nodesString)
        .on('end', () => {
          if (this.isEnqueryPage) {
            removeEvent('.retry', 'click', this.retryHandler)
            removeEvent('.normal', 'click', this.normalHandler)
            removeEvent('.time-node', 'click', this.timeNodeHandler)
            removeEvent('.decision-node', 'click', this.executeBranchHandler)
            addEvent('.retry', 'click', this.retryHandler)
            addEvent('.normal', 'click', this.normalHandler)
            addEvent('.time-node', 'click', this.timeNodeHandler)
            addEvent('.decision-node', 'click', this.executeBranchHandler)
            d3.selectAll('.retry').attr('cursor', 'pointer')
            d3.selectAll('.time-node').attr('cursor', 'pointer')
            d3.selectAll('.decision-node').attr('cursor', 'pointer')
          } else {
            removeEvent('.retry', 'click', this.retryHandler)
            removeEvent('.normal', 'click', this.normalHandler)
            removeEvent('.time-node', 'click', this.timeNodeHandler)
            removeEvent('.decision-node', 'click', this.executeBranchHandler)
          }
           // 新建执行-节点绑定事件
           this.bindFlowEvent()
        })
    },
    async excutionFlow() {
      // 区分已存在的flowInstance执行 和 新建的执行
      if (this.isEnqueryPage) {
        this.processInstance()
        this.showExcution = false
      } else {
        if (!this.selectedTarget || !this.selectedFlow) {
          this.$Message.warning(this.$t('workflow_exec_empty_tip'))
          return
        }
        this.isExecuteActive = true
        const currentTarget = this.allTarget.find(_ => _.id === this.selectedTarget)
        let taskNodeBinds = []
        this.modelData.forEach(_ => {
          const temp = []
          _.refFlowNodeIds.forEach(i => {
            temp.push({
              ..._,
              flowOrderNo: i
            })
          })
          taskNodeBinds = taskNodeBinds.concat(temp)
        })

        const payload = {
          entityDataId: currentTarget.id,
          processSessionId: this.processSessionId,
          entityDisplayName: currentTarget.displayName,
          entityTypeId: this.flowData.rootEntity,
          procDefId: this.flowData.procDefId,
          taskNodeBinds: taskNodeBinds.map(_ => {
            const node = this.flowData.flowNodes.find(node => node.orderedNo === _.flowOrderNo)
            return {
              entityDataId: _.dataId,
              entityTypeId: this.flowData.rootEntity,
              nodeDefId: (node && node.nodeDefId) || '',
              orderedNo: _.flowOrderNo
            }
          })
        }
        this.btnLoading = true
        setTimeout(() => {
          this.btnLoading = false
        }, 5000)
        // 编排执行前，调用试算接口，给出空数据提示
        const previewParams = {
          params: {
            processSessionId: this.processSessionId,
            procDefId: this.flowData.procDefId
          }
        }
        const previewRes = await previewFlowExecute(previewParams)
        const saveFlowInstance = async () => {
          const { status, data } = await createFlowInstance(payload)
          this.btnLoading = false
          this.isExecuteActive = false
          if (status === 'OK') {
            this.$router.push({
              path: '/implementation/workflow-execution/view-execution',
              query: {
                id: data.id,
                from: 'create',
                subProc: this.subProc
              }
            })
          }
        }
        if (previewRes.data && previewRes.data.length > 0) {
          this.$Modal.confirm({
            title: this.$t('workflow_empty_running'),
            content: `${this.$t('workflow_task_node_aspect')}:【${previewRes.data.join('，')}】${this.$t('workflow_empty_running_tip')}`,
            'z-index': 1000000,
            loading: true,
            onOk: async () => {
              this.$Modal.remove()
              saveFlowInstance()
            },
            onCancel: () => {
              this.btnLoading = false
              this.isExecuteActive = false
            }
          })
        } else {
          saveFlowInstance()
        }
      }
    },
    start() {
      if (this.timer === null) {
        this.getStatus()
      }
      if (this.timer !== null) {
        this.stop()
      }
      this.timer = setInterval(() => {
        this.getStatus()
      }, 5000)
    },
    stop() {
      clearInterval(this.timer)
    },
    async getStatus() {
      const found = this.allFlowInstances.find(_ => _.id === this.selectedFlowInstance)
      if (!(found && found.id)) {
        return
      }
      this.intervalLoading = true
      const { status, data } = await getProcessInstance(found.id)
      this.intervalLoading = false
      if (status === 'OK') {
        this.currentInstanceStatusForNodeOperation = data.status
        // 定时刷新详情数据时，同步刷新执行历史下拉列表状态字段
        this.allFlowInstances.forEach(i => {
          if (i.id === data.id) {
            i.displayStatus = data.displayStatus
            i.status = data.status
          }
        })
        if (
          !this.flowData.flowNodes
          || (this.flowData.flowNodes && this.comparativeData(this.flowData.flowNodes, data.taskNodeInstances))
        ) {
          this.flowData = {
            ...data,
            flowNodes: data.taskNodeInstances
          }
          removeEvent('.retry', 'click', this.retryHandler)
          removeEvent('.normal', 'click', this.normalHandler)
          removeEvent('.time-node', 'click', this.timeNodeHandler)
          removeEvent('.decision-node', 'click', this.executeBranchHandler)
          this.initFlowGraph(true)
          this.renderModelGraph()
        }
        // 正在执行分支为判断分支时，拉起分支选择
        const inProcessNode = data.taskNodeInstances.find(
          node => ['decision'].includes(node.nodeType) && node.status === 'InProgress'
        )
        if (
          !['Stop', 'Completed', 'InternallyTerminated', 'Faulted'].includes(
            this.currentInstanceStatusForNodeOperation
          )
          && !this.hasExecuteBranchVisible
          && inProcessNode
        ) {
          this.retryHandler(null, inProcessNode.nodeId)
        }
        if (['Completed', 'InternallyTerminated', 'Faulted'].includes(data.status)) {
          this.stopSuccess = true
          this.stop()
          this.fetchCurrentInstanceStatus()
          this.refreshModelData()
        }
        // this.refreshModelData()
      }
    },
    async refreshModelData() {
      await this.getModelData()
      // this.modelData = []
      if ((!this.selectedFlow || !this.selectedTarget) && !this.isEnqueryPage) {
        this.renderModelGraph()
        return
      }
      if (this.processSessionId) {
        const binds = await getAllBindingsProcessSessionId(this.processSessionId)
        this.allBindingsList = binds.data
        this.renderModelGraph()
      }
    },
    comparativeData(old, newData) {
      let isNew = false
      newData.forEach(_ => {
        const found = old.find(d => d.nodeId === _.nodeId)
        if (found && found.status !== _.status) {
          isNew = true
        }
      })
      return isNew
    },
    processInstance() {
      this.start()
    },
    // 通用节点操作弹框
    async retryHandler(e, id) {
      this.currentFailedNodeID = id || e.target.parentNode.getAttribute('id')
      this.isNodeCanBindData = this.nodesCannotBindData.includes(this.currentFailedNodeID)
      this.retryTargetModelColums[0].disabled = this.isNodeCanBindData
      this.nodeInstance = {}
      this.workflowActionModalVisible = true
      this.targetModalVisible = false
      this.showNodeDetail = false
      const node = this.flowData.flowNodes.find(i => i.nodeId === (id || e.target.parentNode.getAttribute('id'))) || {}
      // 获取节点outline数据(新建执行调用该接口，执行历史单独调用获取节点完整数据)
      this.getOutlineNodeInstance(this.selectedFlow, node)
      // 时间节点手动调过
      if (['timeInterval', 'date'].includes(node.nodeType) && node.status === 'InProgress') {
        this.timeNodeHandler(e)
      }
      // 判断分支-选择执行
      if (['decision'].includes(node.nodeType) && node.status === 'InProgress') {
        this.executeBranchHandler(e, id)
      }
    },
    // 时间节点手动跳过
    async timeNodeHandler(e) {
      const flowInstanceNode = this.flowData.flowNodes.find(
        node => node.nodeId === e.target.parentNode.getAttribute('id')
      )
      if (flowInstanceNode) {
        this.manualSkipParams.act = 'skip'
        this.manualSkipParams.message = ''
        this.manualSkipParams.procInstId = flowInstanceNode.procInstId
        this.manualSkipParams.nodeInstId = flowInstanceNode.id
        const { status, data } = await getExecutionTimeByNodeId(this.manualSkipParams.nodeInstId)
        if (status === 'OK') {
          this.manualSkipParams.dateToDisplay = data
        }
      }
    },
    // 确定手动跳过
    async confirmSkip() {
      const { status } = await skipNode(this.manualSkipParams)
      if (status === 'OK') {
        this.$Notice.success({
          title: 'Success',
          desc: 'Skip action is proceed successfully'
        })
        this.workflowActionModalVisible = false
      }
    },
    // 判断节点-显示可执行分支
    async executeBranchHandler(e, nodeId) {
      const flowInstanceNode = this.flowData.flowNodes
        && this.flowData.flowNodes.find(node => node.nodeId === (nodeId || e.target.parentNode.getAttribute('id')))
      if (flowInstanceNode) {
        this.manualSkipParams.act = 'choose'
        this.manualSkipParams.message = ''
        this.manualSkipParams.procInstId = flowInstanceNode.procInstId
        this.manualSkipParams.nodeInstId = flowInstanceNode.id
        const { status, data } = await getBranchByNodeId(this.manualSkipParams.nodeInstId)
        if (status === 'OK') {
          this.manualSkipParams.branchOption = data
          this.hasExecuteBranchVisible = true
        }
      }
    },
    // 判断分支-执行分支
    async confirmExecuteBranch() {
      const { status } = await executeBranch(this.manualSkipParams)
      if (status === 'OK') {
        this.workflowActionModalVisible = false
        this.$Notice.success({
          title: 'Success',
          desc: 'Execute branch action is proceed successfully'
        })
      }
    },
    // 暂时屏蔽了，没起作用
    normalHandler(e) {
      this.flowGraphMouseenterHandler(e.target.parentNode.getAttribute('id'))
    },
    async workFlowActionHandler(type) {
      const found = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFailedNodeID)
      if (!found) {
        return
      }
      if (type === 'showlog') {
        // 查看日志
        this.flowGraphMouseenterHandler(this.currentFailedNodeID)
      } else if (type === 'skip') {
        // 节点跳过
        this.$Modal.confirm({
          title: this.$t('confirm_to_skip'),
          'z-index': 100,
          onOk: async () => {
            const payload = {
              act: type,
              nodeInstId: found.id,
              procInstId: found.procInstId
            }
            this.btnLoading = true
            setTimeout(() => {
              this.btnLoading = false
            }, 5000)
            const { status } = await retryProcessInstance(payload)
            this.btnLoading = false
            if (status === 'OK') {
              this.$Notice.success({
                title: 'Success',
                desc: (type === 'retry' ? 'Retry' : 'Skip') + ' action is proceed successfully'
              })
              this.workflowActionModalVisible = false
              this.processInstance()
            }
          },
          onCancel: () => {}
        })
      } else if (type === 'retry') {
        this.executeRetry(found, type)
      } else if (type === 'risky') {
        // 高危确认
        this.executeRisky(found)
      } else {
        const payload = {
          nodeInstId: found.id,
          procInstId: found.procInstId
        }
        this.currentAction = type
        this.currentNodeTitle = `${found.orderedNo}${found.orderedNo ? '、' : ''}${found.nodeName}`
        this.getTaskNodeInstanceExecBindings(payload)
        this.retryTargetModalVisible = true
      }
    },
    async executeRetry(nodeInfo, type) {
      const payload = {
        act: type,
        nodeInstId: nodeInfo.id,
        procInstId: nodeInfo.procInstId
      }
      this.btnLoading = true
      setTimeout(() => {
        this.btnLoading = false
      }, 5000)
      const { status } = await retryProcessInstance(payload)
      this.btnLoading = false
      if (status === 'OK') {
        this.$Notice.success({
          title: 'Success',
          desc: (type === 'retry' ? 'Retry' : 'Skip') + ' action is proceed successfully'
        })
        this.workflowActionModalVisible = false
        this.processInstance()
      }
    },
    async executeRisky(nodeInfo) {
      this.confirmModal.message = ''
      this.confirmModal.requestBody = ''
      this.confirmModal.check = false
      const { status, data } = await getNodeContext(nodeInfo.procInstId, nodeInfo.id)
      if (status === 'OK') {
        this.confirmModal.message = data && data[0] && data[0].errorMessage
        this.confirmModal.check = false
        this.confirmModal.isShowConfirmModal = true
        this.confirmModal.requestBody = nodeInfo
      }
    },
    confirmToExecution() {
      this.confirmModal.isShowConfirmModal = false
      this.executeRetry(this.confirmModal.requestBody, 'retry')
    },
    async getTaskNodeInstanceExecBindings(payload) {
      const erroInfo = await this.getErrorLog(this.currentFailedNodeID)
      const { status, data } = await getTaskNodeInstanceExecBindings(payload)
      if (status === 'OK') {
        this.retryTartetModels = data
        this.retryTartetModels.forEach(item => {
          item._checked = false
        })
        this.retryCatchNodeTableList = JSON.parse(JSON.stringify(data))
        this.retryCatchNodeTableList.forEach((tm, index) => {
          tm._checked = false
          const find = erroInfo.find(info => info.id === tm.entityDataId)
          const retryTartetModelsSingle = this.retryTartetModels[index]
          if (find) {
            if (find.errorCode === '-1') {
              tm.confirmToken = 'Y'
              retryTartetModelsSingle.status = 'Confirm'
            } else if (find.errorCode === '1') {
              tm.confirmToken = ''
              retryTartetModelsSingle.status = 'Error'
            } else if (find.errorCode === '0') {
              tm.confirmToken = ''
              retryTartetModelsSingle.status = ''
            } else {
              tm.confirmToken = ''
              retryTartetModelsSingle.status = ''
            }
            retryTartetModelsSingle.message = find.errorMessage
          } else {
            tm.confirmToken = ''
            retryTartetModelsSingle.status = ''
            retryTartetModelsSingle.message = ''
          }
          this.retryTartetModels.forEach(cn => {
            if (tm.id === cn.id && tm.bound === 'Y' && tm.confirmToken === '') {
              cn._checked = true
            }
          })
        })
      }
    },
    async getErrorLog(id) {
      const found = this.flowData.flowNodes.find(_ => _.nodeId === id)
      const { status, data } = await getNodeContext(found.procInstId, found.id)
      if (status === 'OK') {
        const errorInfo = data
          && data[0]
          && data[0].requestObjects.map(item => ({
            id: item.callbackParameter,
            errorMessage: (item.outputs[0] && item.outputs[0].errorMessage) || '',
            errorCode: (item.outputs[0] && item.outputs[0].errorCode) || ''
          }))
        return errorInfo
      }
    },
    // 新建执行-节点点击事件
    bindFlowEvent() {
      if (this.isEnqueryPage !== true) {
        addEvent('.flow', 'mouseover', e => {
          e.preventDefault()
          e.stopPropagation()
          d3.selectAll('g').attr('cursor', 'pointer')
        })
        removeEvent('.flow', 'click', this.flowNodesClickHandler)
        addEvent('.flow', 'click', this.flowNodesClickHandler)
      } else {
        removeEvent('.flow', 'click', this.flowNodesClickHandler)
      }
    },
    // 获取执行记录节点信息和API调用
    flowGraphMouseenterHandler(id) {
      clearTimeout(this.flowDetailTimer)
      this.flowDetailTimer = setTimeout(async () => {
        const found = this.flowData.flowNodes.find(_ => _.nodeId === id)
        this.nodeTitle = (found.orderedNo ? found.orderedNo + '、' : '') + found.nodeName
        const { status, data } = await getNodeContext(found.procInstId, found.id)
        if (status === 'OK') {
          this.executeHistory = deepClone(data || []) // 编排执行记录(节点重试可能有多条记录)
          this.executeHistory = data
            && data.map((item, index) => {
              item.id = (new Date().getTime() + index).toString()
              item.nodeDetailResponseHeader = deepClone(item)
              delete item.nodeDetailResponseHeader.requestObjects
              item.nodeDetailIO = (item.requestObjects
                  && item.requestObjects.map(ro => {
                    ro['inputs'] = this.replaceParams(ro['inputs'])
                    ro['outputs'] = this.replaceParams(ro['outputs'])
                    return ro
                  }))
                || []
              return item
            })
          this.nodeDetailResponseHeader = deepClone(this.executeHistory[0] || {})
          this.pluginInfo = this.nodeDetailResponseHeader.pluginInfo
          this.currentExeHistoryNodeId = this.nodeDetailResponseHeader.id || ''
          delete this.nodeDetailResponseHeader.requestObjects
          // 日志input output表格添加子编排查看按钮
          if (this.nodeDetailResponseHeader && this.nodeDetailResponseHeader.nodeType === 'subProc') {
            const hasFlag = this.nodeDetailColumns.some(i => i.key === 'procDefId')
            if (!hasFlag) {
              this.nodeDetailColumns.push({
                title: this.$t('child_workflow'),
                key: 'procDefId',
                width: 200,
                render: (h, params) => {
                  let procDefName = ''
                  let procInsId = ''
                  let version = ''
                  if (Array.isArray(params.row.outputs) && params.row.outputs.length > 0) {
                    procDefName = params.row.outputs[0].procDefName || '-'
                    procInsId = params.row.outputs[0].procInsId || ''
                    version = params.row.outputs[0].version || ''
                  }
                  return (
                    <span
                      style="cursor:pointer;color:#5cadff;"
                      onClick={() => {
                        this.viewSubProcExecutionDetail(procInsId)
                      }}
                    >
                      {procDefName}
                      <Tag style="margin-left:2px">{version}</Tag>
                    </span>
                  )
                }
              })
            }
          } else {
            this.nodeDetailColumns = this.nodeDetailColumns.filter(i => i.key !== 'procDefId')
          }
        }
        this.tableMaxHeight = 400
      }, 200)
    },
    replaceParams(obj) {
      const placeholder = new Array(16).fill('&nbsp;')
      placeholder.unshift('<br/>')
      for (const key in obj) {
        if (obj[key] !== null && typeof obj[key] === 'string') {
          obj[key] = obj[key].replace('\r\n', placeholder.join(''))
        }
      }
      return obj
    },
    flowNodesClickHandler(e) {
      e.preventDefault()
      e.stopPropagation()
      const g = e.currentTarget
      this.currentFlowNodeId = g.id
      const currentNode = this.flowData.flowNodes.find(_ => _.nodeId === this.currentFlowNodeId)
      this.nodeInstance = currentNode
      this.currentNodeTitle = `${currentNode.orderedNo}${currentNode.orderedNo ? '、' : ''}${currentNode.nodeName}`
      this.highlightModel(g.id, currentNode.nodeDefId, currentNode.nodeType)
      this.renderFlowGraph()
    },
    async highlightModel(nodeId, nodeDefId, nodeType) {
      const hasModalDataNodes = ['human', 'automatic', 'data', 'subProc']
      if (nodeDefId && this.processSessionId && hasModalDataNodes.includes(nodeType)) {
        const { status, data } = await getDataByNodeDefIdAndProcessSessionId(nodeDefId, this.processSessionId)
        if (status === 'OK') {
          this.tartetModels = data.map(_ => {
            const { nodeType } = this.flowData.flowNodes.find(i => i.nodeId === _.nodeDefId) || { nodeType: '' }
            return {
              ..._,
              ...this.modelData.find(j => j.dataId === _.entityDataId),
              nodeType
            }
          })
        } else {
          this.tartetModels = []
        }
      } else {
        return
      }

      this.catchTartetModels = []
      this.catchNodeTableList = []
      this.catchTartetModels = JSON.parse(JSON.stringify(this.tartetModels))
      this.isNodeCanBindData = this.nodesCannotBindData.includes(nodeId)
      this.targetModelColums[0].disabled = this.isNodeCanBindData
      this.targetModalVisible = true
      this.showNodeDetail = false
      this.$nextTick(() => {
        const objData = this.$refs.selection.objData // 表格所有复选框数据(包括未勾选的)
        Object.keys(objData).forEach(i => {
          this.tartetModels.forEach(j => {
            if (j.nodeDefId === nodeDefId && j.entityDataId === objData[i].entityDataId) {
              if (j.bound === 'Y') {
                this.$set(j, '_checked', true)
                objData[i]._isChecked = true
                this.catchNodeTableList.push(objData[i])
              }
            }
          })
        })
      })
    },
    reloadGraph() {
      const graphEl = document.getElementById('graph')
      const initEvent = () => {
        const graph = d3.select('#graph')
        graph.on('dblclick.zoom', null).on('wheel.zoom', null)
          .on('mousewheel.zoom', null)
        this.graph.graphviz = graph
          .graphviz()
          .fit(true)
          .zoom(true)
          .height(graphEl.offsetHeight - 10)
          .width(graphEl.offsetWidth - 10)
      }
      initEvent()
    },
    // initModelGraph() {
    //   const graphEl = document.getElementById('graph')
    //   const initEvent = () => {
    //     const graph = d3.select('#graph')
    //     graph.on('dblclick.zoom', null).on('wheel.zoom', null)
    //       .on('mousewheel.zoom', null)
    //     this.graph.graphviz = graph
    //       .graphviz()
    //       .fit(true)
    //       .zoom(true)
    //       .height(graphEl.offsetHeight - 10)
    //       .width(graphEl.offsetWidth - 10)
    //   }
    //   initEvent()
    //   this.formatRefNodeIds()
    //   this.renderModelGraph()
    // },
    initFlowGraph: debounce(function(excution = false) {
      const graphEl = document.getElementById('flow')
      const graph = d3.select('#flow')
      graph.on('dblclick.zoom', null)
      this.flowGraph.graphviz = graph
        .graphviz()
        .fit(true)
        .zoom(true)
        .height(graphEl.offsetHeight - 10)
        .width(graphEl.offsetWidth - 10)
      this.renderFlowGraph(excution)
    }, 300),
    zoomModal() {
      this.tableMaxHeight = document.body.scrollHeight - 410
      this.nodeDetailFullscreen = true
    },
    // 暂停、继续编排
    async flowControlHandler(operateType) {
      const instance = this.allFlowInstances.find(_ => _.id === this.selectedFlowInstance)
      this.$Modal.confirm({
        title:
          localStorage.getItem('username') !== instance.operator
            ? this.$t('be_workflow_non_owner_title')
            : this.$t('bc_confirm') + ' ' + (operateType === 'stop' ? this.$t('pause') : this.$t('bc_continue')),
        content:
          localStorage.getItem('username') !== instance.operator
            ? `${this.$t('be_workflow_non_owner_list_tip1')}[${instance.operator}]${this.$t(
              'be_workflow_non_owner_list_tip2'
            )}`
            : '',
        'z-index': 10,
        onOk: async () => {
          const payload = {
            procInstId: this.selectedFlowInstance,
            act: operateType
          }
          this.loading = true
          const { status } = await pauseAndContinueFlow(payload)
          this.loading = false
          if (status === 'OK') {
            this.$Notice.success({
              title: 'Success',
              desc: 'Success'
            })
            this.currentInstanceStatusForNodeOperation = operateType === 'stop' ? 'Stop' : 'InProgress'
            this.fetchCurrentInstanceStatus()
          }
        },
        onCancel: () => {}
      })
    },
    // 终止操作
    async stopHandler() {
      const instance = this.allFlowInstances.find(_ => _.id === this.selectedFlowInstance)
      this.$Modal.confirm({
        title:
          localStorage.getItem('username') !== instance.operator
            ? this.$t('be_workflow_non_owner_title')
            : this.$t('bc_confirm') + ' ' + this.$t('stop_orch'),
        content:
          localStorage.getItem('username') !== instance.operator
            ? `${this.$t('be_workflow_non_owner_list_tip1')}[${instance.operator}]${this.$t(
              'be_workflow_non_owner_list_tip2'
            )}`
            : '',
        'z-index': 10,
        onOk: async () => {
          // createWorkflowInstanceTerminationRequest
          const payload = {
            procInstId: this.selectedFlowInstance,
            procInstKey: instance.procInstKey
          }
          this.loading = true
          const { status } = await createWorkflowInstanceTerminationRequest(payload)
          this.loading = false
          if (status === 'OK') {
            this.fetchCurrentInstanceStatus()
            this.stopSuccess = true
            this.$Notice.success({
              title: 'Success',
              desc: 'Success'
            })
          }
        },
        onCancel: () => {}
      })
    },
    handleLinkItsmDetail(row) {
      const detailRouteMap = {
        1: 'detailPublish',
        2: 'detailRequest',
        3: 'detailProblem',
        4: 'detailEvent',
        5: 'detailChange'
      }
      window.sessionStorage.currentPath = '' // 先清空session缓存页面，不然打开新标签页面会回退到缓存的页面
      const path = `${window.location.origin}/#/taskman/workbench/${detailRouteMap[row.type]}?requestId=${
        row.id
      }&requestTemplate=${row.requestTemplate}`
      window.open(path, '_blank')
    },
    getSubProcItem(item) {
      this.subProcItem = item
    }
  }
}
</script>
<style lang="scss">
.common-base-drawer {
  .jv-container .jv-code {
    overflow: hidden;
    padding: 0px 10px !important;
  }
  .ivu-form-item {
    margin-bottom: 8px;
  }
  .common-ui-header-title {
    padding-bottom: 3px;
  }
  .ivu-drawer-body {
    padding: 16px 8px;
  }
}
</style>
<style lang="scss" scoped>
.workflow-execution {
  &-header {
    display: flex;
  }
  .back-header {
    width: 30px;
    display: flex;
    align-items: center;
    margin-bottom: 8px;
    .icon {
      cursor: pointer;
      width: 28px;
      height: 24px;
      color: #fff;
      border-radius: 2px;
      background: #5384ff;
    }
    .name {
      font-size: 16px;
      margin-left: 16px;
      display: flex;
      align-items: center;
    }
  }
  .form {
    flex: 1;
  }
}
body {
  color: #e5f173; //#15a043;
}
.pages ::v-deep .ivu-select-dropdown {
  height: 500px !important;
}
::v-deep .jv-code {
  padding: 10px 10px !important;
}
.header-icon {
  margin: 3px 40px 0 0 !important;
}
#graph-container {
  border: 1px solid #d3cece;
  border-radius: 3px;
  padding: 5px;
  height: calc(100vh - 180px);
}
.model_target .ivu-modal-content-drag {
  right: 40px;
}
.pages ::v-deep .ivu-card-body {
  padding: 8px;
}
.ivu-form-item {
  margin-bottom: 0 !important;
  padding-left: 15px;
}
.excution-serach {
  margin: 5px 6px 0 0;
}
.graph-container {
  overflow: auto;
}
.header-icon {
  float: right;
  margin: 3px 20px 0 0;
}
.reset-button {
  position: absolute;
  right: 20px;
  bottom: 5px;
  font-size: 12px;
}
.set-data-title {
  position: absolute;
  left: 10px;
  top: 5px;
  font-size: 14px;
}
.set-data-button {
  position: absolute;
  right: 10px;
  top: 5px;
  font-size: 12px;
}
.execution-history {
  padding: 0 15px;
  margin-top: -10px;
  .header-title {
    font-size: 15px;
    font-weight: bold;
    padding-bottom: 10px;
  }
}
</style>
